﻿using System;
using System.Diagnostics;

namespace Microsoft.Goldenlight.Framework
{
    /// <summary>
    /// 	Base class which provides naming and object state machine capability.
    /// </summary>
    public abstract class LifetimeObject : ICloneable, IDisposable
    {
        #region Status Fields & Properties

        private bool _isLoaded;

        /// <summary>
        /// 	Gets the current load state of the lifetime-oriented object.
        /// </summary>
        [CloneIgnore]
        public bool IsLoaded
        {
            get
            {
                return _isLoaded;
            }
        }

        private bool _isPoolable;

        /// <summary>
        /// 	True if object pooling is turned on for the object.
        /// </summary>
        public bool IsPoolable
        {
            get
            {
                return _isPoolable;
            }
            set
            {
                _isPoolable = value;
            }
        }

        #endregion

        #region Constructors

        /// <summary>
        /// 	Allows a LifetimeObject to attempt to free resources and perform other cleanup operations before garbage collection reclaims the LifetimeObject.
        /// </summary>
        ~LifetimeObject()
        {
            Dispose(false);
        }

        #endregion

        #region Implementation of ICloneable

        /// <summary>
        /// 	Creates a new object that is a copy of the current instance.
        /// </summary>
        /// <returns> A new object that is a copy of this instance. </returns>
        public virtual LifetimeObject Clone()
        {
            var clonedObject = ObjectPooler.Create(GetObjectType());
            CopyTo(clonedObject);
            return clonedObject;
        }

        /// <summary>
        /// 	Called by Clone. Objects need to implement this method to copy all public properties not marked with TorqueCloneIgnore attribute.
        /// </summary>
        /// <param name="newLifetimeObject"> The object to be copied over. </param>
        public virtual void CopyTo(LifetimeObject newLifetimeObject)
        {
            if (newLifetimeObject == null)
            {
                throw new ArgumentNullException("newLifetimeObject");
            }
            if (newLifetimeObject.IsDisposed)
            {
                throw new ObjectDisposedException("newLifetimeObject");
            }
            newLifetimeObject._name = string.Empty;
            newLifetimeObject._isPoolable = _isPoolable;
            newLifetimeObject._isDisposed = false;
        }

        #endregion

        #region Implementation of IDisposable

        #region Fields & Properties

        protected bool _isDisposed;

        /// <summary>
        /// 	Indicates if the object has been disposed.
        /// </summary>
        public bool IsDisposed
        {
            get
            {
                return _isDisposed;
            }
            internal set
            {
                _isDisposed = value;
            }
        }

        #endregion

        #region Dispose

        /// <summary>
        /// 	Releases all resources used by the LifetimeObject class.
        /// </summary>
        /// <param name="disposing"> true to release both managed and unmanaged resources; false to release only unmanaged resources. </param>
        protected virtual void Dispose(bool disposing)
        {
            _isDisposed = true;
            if (disposing)
            {
                //NameChanging = null;
                //NameChanged = null;
                _name = string.Empty;
                Unload();
                //Loaded = null;
                //Unloaded = null;
                if (_isPoolable)
                {
                    ObjectPooler.Return(GetObjectType(), this);
                    //_isPoolable = false;
                }
            }
        }

        #endregion

        /// <summary>
        /// 	Immediately releases the unmanaged resources used by this object.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        #endregion

        #region Naming Operations

        #region Fields & Properties

        private string _name = string.Empty;

        /// <summary>
        /// 	Name of object. Object can be looked up in ObjectManager by name.
        /// </summary>
        [CloneIgnore]
        public string Name
        {
            get
            {
                return _name;
            }
            set
            {
                var oldValue = _name;
                var newValue = value ?? string.Empty;
                if (oldValue != newValue)
                {
                    OnNameChanging(oldValue, newValue);
                    _name = newValue;
                    OnNameChanged(oldValue, newValue);
                }
            }
        }

        /// <summary>
        /// 	Automatically generated name for object. AutoNames are not stored in the object name manager, so you cannot find an object using this name.
        /// </summary>
        /// <remarks>
        /// 	This exists purely for debugging purposes: when you are inspecting an object in the debugger, it makes it easier to tell what kind of object you are dealing with. This can be overriden in subclasses to provide more information in the name that the base implementation below. See StaticSprite for an example override.
        /// </remarks>
        [CloneIgnore]
        public virtual string AutoName
        {
            get
            {
                return string.Format("{0}_{1}", GetType().Name, GetHashCode());
            }
        }

        #endregion

        #region Events

        /// <summary>
        /// 	Raised when the name of object is being updated.
        /// </summary>
        public event EventHandler<PropertyChangeEventArgs<string>> NameChanging;

        /// <summary>
        /// 	Raises the NameChanging event. Override this method to add code to handle when the name of object is updated.
        /// </summary>
        /// <param name="oldName"> Old name argument for the NameChanging event. </param>
        /// <param name="newName"> New name argument for the NameChanging event. </param>
        protected virtual void OnNameChanging(string oldName, string newName)
        {
            var onNameChanging = NameChanging;
            if (onNameChanging != null)
            {
                onNameChanging(this, new PropertyChangeEventArgs<string>("Name", oldName, newName));
            }
        }

        /// <summary>
        /// 	Raised when the name of object is updated.
        /// </summary>
        public event EventHandler<PropertyChangeEventArgs<string>> NameChanged;

        /// <summary>
        /// 	Raises the NameChanged event. Override this method to add code to handle when the name of object is updated.
        /// </summary>
        /// <param name="oldName"> Old name argument for the NameChanging event. </param>
        /// <param name="newName"> New name argument for the NameChanging event. </param>
        protected virtual void OnNameChanged(string oldName, string newName)
        {
            var onNameChanged = NameChanged;
            if (onNameChanged != null)
            {
                onNameChanged(this, new PropertyChangeEventArgs<string>("Name", oldName, newName));
            }
        }

        #endregion

        #endregion

        #region Object Type Operations

        /// <summary>
        /// 	Gets the type of lifetime object.
        /// </summary>
        /// <returns> </returns>
        protected virtual Type GetObjectType()
        {
            return GetType();
        }

        #endregion

        #region Load States Operations

        #region Events

        /// <summary>
        /// 	Occurs when the lifetime object completes its transition from the loading state into the loaded state.
        /// </summary>
        public event EventHandler<EventArgs> Loaded;

        /// <summary>
        /// 	Invoked during the transition of a lifetime object into the loaded state.
        /// </summary>
        protected virtual void OnLoaded()
        {
            var onLoaded = Loaded;
            if (onLoaded != null)
            {
                onLoaded(this, EventArgs.Empty);
            }
        }

        /// <summary>
        /// 	Occurs when the lifetime object completes its transition from the unloading state into the unloaded state.
        /// </summary>
        public event EventHandler<EventArgs> Unloaded;

        /// <summary>
        /// 	Invoked during the transition of a lifetime object into the unloaded state.
        /// </summary>
        protected virtual void OnUnloaded()
        {
            var onUnloaded = Unloaded;
            if (onUnloaded != null)
            {
                onUnloaded(this, EventArgs.Empty);
            }
        }

        #endregion

        /// <summary>
        /// 	Inserts processing on a lifetime object after it transitions into the loading state which must complete within a specified interval of time.
        /// </summary>
        protected virtual void OnLoad()
        {
        }

        /// <summary>
        /// 	Inserts processing on a lifetime object after it transitions to the unloading state due to the invocation of a synchronous unload operation.
        /// </summary>
        protected virtual void OnUnload()
        {
        }

        /// <summary>
        /// 	Causes a lifetime object to transition from the created state into the loaded state.
        /// </summary>
        public bool Load()
        {
            if (!_isLoaded)
            {
                try
                {
                    OnLoad();
                    _isLoaded = true;
                    OnLoaded();
                    return true;
                }
                catch (Exception ex)
                {
                    //TODO: Trace
                    Debug.WriteLine(ex.Message);
                    return false;
                }
            }
            return false;
        }

        /// <summary>
        /// 	Causes a lifetime object to transition from its current state into the unloaded state.
        /// </summary>
        public void Unload()
        {
            if (_isLoaded)
            {
                try
                {
                    OnUnload();
                    _isLoaded = false;
                    OnUnloaded();
                }
                catch (Exception ex)
                {
                    //TODO: Trace
                    Debug.WriteLine(ex.Message);
                }
            }
        }

        #endregion
    }
}